from __future__ import print_function
from myAI.synchronize.RAE import RAE1,EndCriticalRegion
# from myAI.synchronize.RAE import ipcArgs
from myAI.synchronize.RAE import IpcArgs
import myAI.synchronize.domain
import threading
import inspect
import ctypes

__author__ = 'chengkai'

problem_module = None

class Agenda():
    def __init__(self):
        self.lastActiveStack = 0        # keeps track of the last stack that was Progressed
        self.numstacks = 0              # keeps track of the total number of stacks
        # 保存上层AI分配的任务，每个算子当前只有一个任务,例如{14983: ['maneuver', 2517]}
        self.tasks = {}
        # 用于打印输出，不是很重要
        self.taskInfo = {}
        #保存不同任务堆栈线程的共享变量，以便于agenda管理
        # self.ipcArgs = ipcArgs
        self.ipcArgs = IpcArgs()

    def agenda_reset(self):
        self.terminate_Allthread()
        self.lastActiveStack = None
        self.numstacks = None
        self.tasks = None
        self.taskInfo = None
        # self.ipcArgs.ipcargs_reset()
        self.ipcArgs = None


    def InitializeDomain(self,domain, problem):
        '''
        :param domain: code of the domain which you are running
        :param problem: id of the problem
        :return:none
        '''
        if domain in ['CR', 'SD', 'EE', 'IP', 'OF', 'SR', 'SDN', 'test', 'testInstantiation', 'SR2']:
            module = problem + '_' + domain
            global problem_module
            # ReinitializeState()  # useful for batch runs to start with the first state
            problem_module = __import__(module)
            problem_module.ResetState()
            return problem_module
        else:
            print("Invalid domain\n", domain)
            exit(11)


    def CreateNewStack(self,taskInfo, raeArgs, myAgent):
        stackid = raeArgs.stack
        retcode = RAE1(raeArgs.task, raeArgs, myAgent)
        # retcode, retryCount, height, taskCount, commandCount = RAE1(raeArgs.task, raeArgs,myAgent)
        # taskInfo[stackid] = ([raeArgs.task] + raeArgs.taskArgs, retcode, retryCount, height, taskCount, commandCount)


        # 如果有command失败，最终回溯到最顶层的任务，即上层AI分配给该算子的任务,返回值为Failure，线程随即结束
        # 需要告知上层AI，本算子任务是正常结束还是失败
        if retcode == 'Success':
            # print(f'Task success: obj_id = {stackid}, task = {self.tasks[stackid]}')
            print(f'Success: obj_id = {stackid}, task = {self.ipcArgs.threadDict[stackid][-1]}')
            # self.ipcArgs.threadDict[stackid] = ['Success']
            self.tasks[stackid] = ['Success']
        elif retcode == 'Failure':
            # print(f'Task failure: obj_id = {stackid}, task = {self.tasks[stackid]}')
            print(f'Success: obj_id = {stackid}, task = {self.ipcArgs.threadDict[stackid][-1]}')
            # self.ipcArgs.threadDict[stackid] = ['Failure']
            self.tasks[stackid] = ['Failure']
        # 如果线程执行异常错误，告知上层AI，更改任务状态
        # elif retcode == 'Error':
        #     print(f'Thread error: obj_id = {stackid}, task = {self.tasks[stackid]}')
        #     self.tasks[stackid] = ['Error']
        else:
            pass
        EndCriticalRegion(self.ipcArgs)

    def PrintResult(self,taskInfo):
        print('ID ', '\t', 'Task',
              '\t\t\t', 'Result',
              '\t\t\t', 'Retry Count',
              '\t\t\t', 'Efficiency',
              '\t\t\t', 'h',
              '\t\t\t', 't',
              '\t\t\t', 'c',
              '\n')
        for stackid in taskInfo:
            args, res, retryCount, eff, height, taskCount, commandCount = taskInfo[stackid]

            print(stackid, '\t', 'Task {}{}'.format(args[0], args[1:]),
                  '\t\t\t', res,
                  '\t\t\t', retryCount,
                  '\t\t\t', eff,
                  '\t\t\t', height,
                  '\t\t\t', taskCount,
                  '\t\t\t', commandCount,
                  '\n')


    def PrintResultSummary(self,taskInfo):
        succ = 0
        fail = 0
        retries = 0
        effTotal = 0
        h = 0
        t = 0
        c = 0
        for stackid in taskInfo:
            args, res, retryCount, eff, height, taskCount, commandCount = taskInfo[stackid]
            if res == 'Success':
                succ += 1
            else:
                fail += 1
            retries += retryCount
            effTotal += eff.GetValue()
            c += commandCount
            t += taskCount
            if height > h:
                h = height
        print(succ, succ + fail, retries, h, t, c)
        # print(' '.join('-'.join([key, str(cmdNet[key])]) for key in cmdNet))



    def raeMult(self,myAgent):

        #对于正在运行的线程其id如果不在上层分配的任务中，说明该算子已经死亡，应将该线程终止
        tmpPop = [thread_obj_id for thread_obj_id in self.ipcArgs.threadDict.keys() if thread_obj_id not in self.tasks.keys()]
        for thread_obj_id in tmpPop:
            self.terminate_thread(self.ipcArgs.threadDict[thread_obj_id][0])
            print(f'obj_id = {thread_obj_id} is Dead!!!  StopTask = {self.ipcArgs.threadDict[thread_obj_id][1]}')
            self.ipcArgs.threadDict.pop(thread_obj_id)

        # 当前算子总任务数
        self.numstacks = len(self.tasks)
        for obj_id, taskParams in self.tasks.items():
            while (self.numstacks > 0):
                #激活权限在主线程时
                if self.ipcArgs.nextStack == -1:
                    self.ipcArgs.sem.acquire()
                    #如果算子对应线程不存在，则需要创建对应线程
                    if obj_id not in self.ipcArgs.threadDict.keys():
                        self.BeginCreateStack(obj_id, taskParams, myAgent)
                        print(f'Begin: obj_id = {obj_id},task = {self.ipcArgs.threadDict[obj_id][1]}')
                    #如果算子对应线程存在
                    else:
                        #分配任务跟之前堆栈任务相同，继续执行
                        # 列表的比较，如果上层AI分配的是新任务，taskParams指向新的地址，与threadDict中任务列表的原地址是不同的，因此可据此比较
                        if taskParams == self.ipcArgs.threadDict[obj_id][1]:
                            # 如果该线程是活的，则继续执行
                            if self.ipcArgs.threadDict[obj_id][0].isAlive():
                                # 主线程将激活权限给子线程
                                self.ipcArgs.sem.release()
                                self.ipcArgs.nextStack = obj_id
                                self.lastActiveStack = obj_id
                            # 如果该线程已经死亡，则需要重新创建该线程
                            else:
                                self.BeginCreateStack(obj_id, taskParams, myAgent)
                                print(f'Begin again: obj_id = {obj_id},task = {self.ipcArgs.threadDict[obj_id][1]}')
                        #分配任务跟之前堆栈任务不同，终止之前的任务堆栈线程，重新创建新的线程
                        else:
                            print(f'Stop last: obj_id = {obj_id},task = {self.ipcArgs.threadDict[obj_id][1]}')
                            self.terminate_thread(self.ipcArgs.threadDict[obj_id][0])
                            self.ipcArgs.threadDict.pop(obj_id)
                            self.BeginCreateStack(obj_id, taskParams, myAgent)
                            print(f'Begin new: obj_id = {obj_id},task = {self.ipcArgs.threadDict[obj_id][1]}')
                    self.numstacks = self.numstacks - 1
                    break
        # 要等待最后一个线程返回结果后,即将激活权限交还给主线程后，将全部total_actions返回
        while(self.ipcArgs.nextStack != -1):
            pass

        #堆栈线程不是alive有两种情况：任务执行成功、任务执行失败，不在将算子id从tasks中pop出，在threadDict都pop出来
        needPop = [obj_id for obj_id, thread in self.ipcArgs.threadDict.items() if not thread[0].isAlive()]
        for obj_id in needPop:
            # self.tasks.pop(obj_id)
            if self.ipcArgs.threadDict[obj_id][0] == 'Error':
                print(f'thread error: obj_id = {obj_id}')
            self.ipcArgs.threadDict.pop(obj_id)
            # print(f'threadDict complete: obj_id = {obj_id}', f'tasks remain: {self.tasks.keys()}')

        return self.ipcArgs.total_actions  # for unit tests

    #创建任务堆栈线程，并激活该线程执行command
    def BeginCreateStack(self, obj_id, taskParams, myAgent):
        '''

        :param obj_id: 算子ID
        :param taskParams: 上层AI分配的任务，包括任务名称和参数
        :param myAgent: AI对象
        :return:
        '''
        if taskParams != []:
            #创建局部对象，用于保存任务信息，作为参数传给CreateNewStack
            raeArgs = RaeArgs()
            # 用obj_id作为堆栈标识
            raeArgs.stack = obj_id
            # 任务名称
            raeArgs.task = taskParams[0]
            # 任务参数，包括obj_id
            raeArgs.taskArgs = [obj_id] + taskParams[1:]
            # 创建子线程，并将线程对象和任务保存在threadDict中
            self.ipcArgs.threadDict[obj_id] = [threading.Thread(target=self.CreateNewStack, args=(self.taskInfo, raeArgs, myAgent)), taskParams]
            # # 设置子线程为守护线程，在主线程结束后，任务堆栈子线程此时被回收
            # self.ipcArgs.threadDict[obj_id][0].setDaemon(True)
            self.ipcArgs.threadDict[obj_id][0].start()
            # 主线程将激活权限给子线程
            self.ipcArgs.sem.release()
            self.ipcArgs.nextStack = obj_id
            self.lastActiveStack = obj_id



    def terminate_thread(self,thread):
        if not thread.isAlive():
            return
        exc = ctypes.py_object(SystemExit)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread.ident), exc)
        if res == 0:
            raise ValueError("nonexistent thread id")
        elif res > 1:
            ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")

    def terminate_Allthread(self):
        for thread_and_task in self.ipcArgs.threadDict.values():
            if thread_and_task[0].isAlive():
                self.terminate_thread(thread_and_task[0])


# 创建全局对象
# agenda = Agenda()

# 记录本线程的基本信息
class RaeArgs():
    def __init__(self):
        pass